﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using HeadCounter.Armourer;
using HeadCounter.Armourer.Base;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data.Domain;
using MathematicallySafe.HeadCounter.Data.Interfaces;
using MathematicallySafe.HeadCounter.Data.Parsers;
using StructureMap;

namespace MathematicallySafe.HeadCounter.Data.Models
{
    public class GuildModel : IGuild
    {
        private IOptions _options;
        private IArmouryService _armouryService;

        private Guild _guild;

        private bool _isDirty;
        private readonly object _sync = new object();

        private CharacterXmlParser _xmlParser = new CharacterXmlParser();

        public SerializableSortedList<string, Raider> Roster
        {
            get { return _guild.Roster; }
            private set { _guild.Roster = value;}
        }

        public GuildModel(IOptions options, IArmouryService armouryService)
        {
            _options = options;
            _armouryService = armouryService;

            if (File.Exists(Constants.GuildFileName))
            {
                Load();                
            }
            else
            {
                CreateDefault();    
            }
        }

        private void CreateDefault()
        {
            _guild = new Guild();
            Name = "<Guild>";
            Roster = new SerializableSortedList<string, Raider>();
        }

        public void SubscribeEvents()
        {
            _armouryService.CharacterDownloaded += ArmouryServiceCharacterDownloaded;
        }

        public void UnsubscribeEvents()
        {
            _armouryService.CharacterDownloaded -= ArmouryServiceCharacterDownloaded;
        }


        public string Name
        {
            get { return _guild.Name; }
            set { _guild.Name = value; }
        }

        #region Raiders

        public int NumberOfRaiders
        {
            get
            {
                lock (_sync)
                {
                    return Roster.Count();
                }
            }
        }

        public IEnumerable<Raider> Raiders
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> ActiveRaiders
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                where r.IsInactive == false
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> RaidersByMain
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                orderby r.MainName ascending
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> ActiveRaidersByMain
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                where r.IsInactive == false
                                orderby r.MainName ascending
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public void AddRaider(string characterName, CharacterClass characterClass, CharacterRace characterRace,
                              DateTime joined)
        {
            if (IsMember(characterName))
                throw new ArgumentException("Raider " + characterName + " is already on the roster.");

            var character = new Character
                            {
                                Name = characterName,
                                Class = characterClass,
                                Race = characterRace
                            };

            var raider = new Raider(character)
                         {
                             Joined = new DateTime(joined.Year, joined.Month, joined.Day)
                         };

            lock (_sync)
            {
                Roster.Add(raider.Identity, raider);
            }

            OnRaiderAdded(new RosterChangedEventArgs(raider));
            DownloadCharacterAsynch(_options.Region, _options.Realm, raider.MainName);
        }

        public void RemoveRaider(string characterName)
        {
            Raider raider = null;

            lock (_sync)
            {
                raider = DoFindRaider(characterName);

                if (raider != null)
                {
                    Roster.Remove(raider.Identity);
                }
            }

            if (raider != null) OnRaiderRemoved(new RosterChangedEventArgs(raider));
        }

        #endregion

        #region Metrics

        public double ShortPeriodLootCountMean
        {
            get { return _guild.ShortPeriodLootCountMean; }
            set { _guild.ShortPeriodLootCountMean = value; }
        }

        public double ShortPeriodLootValueMean
        {
            get { return _guild.ShortPeriodLootValueMean; }
            set { _guild.ShortPeriodLootValueMean = value; }
        }

        public double ShortPeriodLootRateMean
        {
            get { return _guild.ShortPeriodLootRateMean; }
            set { _guild.ShortPeriodLootRateMean = value; }
        }

        public double ShortPeriodWeightedLootRateMean
        {
            get { return _guild.ShortPeriodWeightedLootRateMean; }
            set { _guild.ShortPeriodWeightedLootRateMean = value; }
        }

        public double? ShortPeriodLootPriorityMean
        {
            get { return _guild.ShortPeriodLootPriorityMean; }
            set { _guild.ShortPeriodLootPriorityMean = value; }
        }

        public double LongPeriodLootCountMean
        {
            get { return _guild.LongPeriodLootCountMean; }
            set { _guild.LongPeriodLootCountMean = value; }
        }

        public double LongPeriodLootValueMean
        {
            get { return _guild.LongPeriodLootValueMean; }
            set { _guild.LongPeriodLootValueMean = value; }
        }

        public double LongPeriodLootRateMean
        {
            get { return _guild.LongPeriodLootRateMean; }
            set { _guild.LongPeriodLootRateMean = value; }
        }

        public double LongPeriodWeightedLootRateMean
        {
            get { return _guild.LongPeriodWeightedLootRateMean; }
            set { _guild.LongPeriodWeightedLootRateMean = value; }
        }

        public double? LongPeriodLootPriorityMean
        {
            get { return _guild.LongPeriodLootPriorityMean; }
            set { _guild.LongPeriodLootPriorityMean = value; }
        }

        public double LifetimeLootCountMean
        {
            get { return _guild.LifetimeLootCountMean; }
            set { _guild.LifetimeLootCountMean = value; }
        }

        public double LifetimeLootValueMean
        {
            get { return _guild.LifetimeLootValueMean; }
            set { _guild.LifetimeLootValueMean = value; }
        }

        public double LifetimeLootRateMean
        {
            get { return _guild.LifetimeLootRateMean; }
            set { _guild.LifetimeLootRateMean = value; }
        }

        public double LifetimeWeightedLootRateMean
        {
            get { return _guild.LifetimeWeightedLootRateMean; }
            set { _guild.LifetimeWeightedLootRateMean = value; }
        }

        public double? LifetimeLootPriorityMean
        {
            get { return _guild.LifetimeLootPriorityMean; }
            set { _guild.LifetimeLootPriorityMean = value; }
        }

        public double LootRateStdDev
        {
            get { return _guild.LootRateStdDev; }
            set { _guild.LootRateStdDev = value; }
        }

        public double LootRateLow{ get { return LifetimeLootRateMean - (2.0*LootRateStdDev);} }
        public double LootRateHigh { get { return LifetimeLootRateMean + (2.0*LootRateStdDev);} }

        public double WeightedLootRateStdDev
        {
            get { return _guild.WeightedLootRateStdDev; }
            set { _guild.WeightedLootRateStdDev = value; }
        }

        public double WeightedLootRateLow { get { return LifetimeWeightedLootRateMean - (2.0*WeightedLootRateStdDev);} }
        public double WeightedLootRateHigh { get { return LifetimeWeightedLootRateMean + (2.0 * WeightedLootRateStdDev); } }

        #endregion

        #region Events

        public event EventHandler<RosterChangedEventArgs> RaiderAdded;
        public event EventHandler<RosterChangedEventArgs> RaiderRemoved;
        public event EventHandler<CharacterDownloadedEventArgs> CharacterDownloaded;

        protected virtual void OnRaiderAdded(RosterChangedEventArgs e)
        {
            if (RaiderAdded != null)
            {
                RaiderAdded(this, e);
            }
        }

        protected virtual void OnRaiderRemoved(RosterChangedEventArgs e)
        {
            if (RaiderRemoved != null)
            {
                RaiderRemoved(this, e);
            }
        }

        protected virtual void OnCharacterDownloaded(CharacterDownloadedEventArgs e)
        {
            if (CharacterDownloaded != null)
            {
                CharacterDownloaded(this, e);
            }
        }

        #endregion

        public bool IsMember(string name)
        {
            return FindRaider(name) != null;
        }

        public Raider FindRaider(string characterName)
        {
            lock (_sync)
            {
                return DoFindRaider(characterName);
            }
        }

        private Raider DoFindRaider(string characterName)
        {
            Raider raider;

            if (!Roster.TryGetValue(characterName, out raider))
            {
                raider = (Roster.Values.Where(r => r.HasCharacter(characterName))).FirstOrDefault();
            }

            return raider;
        }

        private Raider DoFindRaiderMain(string mainName)
        {
            return Roster.Values.Where(r => r.MainName == mainName).FirstOrDefault();
        }

        #region Armoury downloads

        public void DownloadCharacterAsynch(WowRegion region, string realm, string name)
        {
            lock (_sync)
            {
                // todo what about downloading armoury data for alts?
                var raider = DoFindRaiderMain(name);

                if (raider == null || !raider.HasDownloadedItems())
                {
                    _armouryService.EnqueueGetCharacter(region, realm, name);
                    _armouryService.RunArmouryServiceAsync();
                }
            }
        }

        void ArmouryServiceCharacterDownloaded(object sender, CharacterDataDownloadedEventArgs e)
        {
            string name;

            try
            {
                name = e.CharacterXml.Element("character").Attribute("name").Value;
            }
            catch (Exception)
            {
                throw new ArgumentException("Could not find character from downloaded XML");
            }

            lock (_sync)
            {
                var raider = DoFindRaiderMain(name);

                if (raider != null)
                {
                    _xmlParser.UpdateCharacter(raider, e.CharacterXml);
                    raider.GearItems.ForEach(gear => _armouryService.EnqueueGetItem(gear.ID));
                }
            }

            OnCharacterDownloaded(new CharacterDownloadedEventArgs(name));
        }

        #endregion

        #region IPersistable

        public bool IsDirty
        {
            get { return _isDirty; }
        }

        public bool IsClean
        {
            get { return !_isDirty; }
        }

        public void SetClean()
        {
            _isDirty = false;
        }

        public void SetDirty()
        {
            _isDirty = true;
        }

        private void Load()
        {
            _guild = SerialisationHelper.Load<Guild>(Constants.GuildFileName);
        }

        public void Save()
        {
            SerialisationHelper.Save(Constants.GuildFileName, _guild);
        }

        #endregion
    }

    public class RosterChangedEventArgs : EventArgs
    {
        public readonly Raider Raider;

        public RosterChangedEventArgs(Raider raider)
        {
            Raider = raider;
        }
    }
}